package model;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.PreRemove;

import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.internal.sessions.remote.SequencingFunctionCall.GetNextValue;

import controller.PersistenceManager;

@Entity
public class Zimmer implements IEntity
{
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	private String name;
	private int maxBelegung;
	private double preisEZ;
	private double preisDZ;
	private double preisDreifachbelegung;
	private double preisEZmitKind;
	private double preisEZmitZweiKindern;
	private double DZMitKind;
	@OneToMany(mappedBy = "zimmer", cascade = CascadeType.ALL)
	private List<Belegung> belegungen = new ArrayList<Belegung>();

	public Zimmer(String name, int maxBelegung, double preisEZ, double preisDZ, double preisDreifachbelegung, double preisEZmitKind,
			double preisEZmitZweiKindern, double dZMitKind, List<Belegung> belegungen)
	{
		super();
		this.name = name;
		this.maxBelegung = maxBelegung;
		this.preisEZ = preisEZ;
		this.preisDZ = preisDZ;
		this.preisDreifachbelegung = preisDreifachbelegung;
		this.preisEZmitKind = preisEZmitKind;
		this.preisEZmitZweiKindern = preisEZmitZweiKindern;
		DZMitKind = dZMitKind;
		this.belegungen = belegungen;
	}

	public Zimmer()
	{
		
	}

	public Zimmer(String name, int maxBelegung)
	{
		super();
		this.name = name;
		this.maxBelegung = maxBelegung;
	}

	// ##################################################################
	// ======================== Business Logic ==========================
	/*
	@PreRemove
	public void beforeRemove()
	{
		for(Belegung b: this.belegungen)
		{
			b.setZimmer(null);
		}
		this.belegungen.clear();
	}
*/
	public void addBelegung(Belegung b)
	{
		this.belegungen.add(b);
		b.setZimmer(this);
	}

	public static List<Zimmer> getAllRooms(){
		// Test version
//		List<Zimmer> result = new ArrayList<Zimmer>();
//		result.add(new Zimmer("101", 1, 60.0, 75.0, 50.0, 70.0, 75.0, 90.0,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("106", 4, 80.0, 95.0, 105.0, 85.0, 90.0, 100.0,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("102", 2, 60.0, 75.0, 50.0, 70.0, 80.0, 85.0,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("105", 2, 60.0, 75.0, 50.0, 70.0, 80.0, 85.0,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("103", 3, 65.0, 75.0, 80.0, 73.0, 80.0, 83.5,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("104", 3, 65.0, 75.0, 80.0, 73.0, 80.0, 83.5,
//				new ArrayList<Belegung>()));
//		result.add(new Zimmer("107", 4, 80.0, 95.0, 105.0, 85.0, 90.0, 100.0,
//				new ArrayList<Belegung>()));
//		return result;
		PersistenceManager pm = PersistenceManager.getInstance();
		return pm.loadAllEntities(new Zimmer());
	 }

	public boolean isFreeInPeriod(Date from, Date to) {
		boolean result = true;

		for (Belegung belegung : belegungen) {
			Date otherTo = belegung.getReservierung().getBis();
			Date otherFrom = belegung.getReservierung().getVon();

			result = result && (from.after(otherTo) || to.before(otherFrom));

			if (!result) {
				break;
			}
		}

		return result;
	}

	public static List<List<Zimmer>> findSuitableRooms(List<Kunde> customers,
			Date from, Date to) {
		List<List<Zimmer>> roomResults = new ArrayList<List<Zimmer>>();
		int children, adults;

		int[] adultsAndChildren = Kunde.getNumerOfAdultsAndChildren(customers);
		adults = adultsAndChildren[0];
		children = adultsAndChildren[1];

		// Find free rooms
		List<Zimmer> possibleRooms = findAvailableRooms(from, to); 
		// Did we found any free room?
		if (possibleRooms.size() > 0) {
			// Sort the room after size, biggest first
			Collections.sort(possibleRooms, new Comparator<Zimmer>() {

				@Override
				public int compare(Zimmer o1, Zimmer o2) {
					return o2.maxBelegung - o1.maxBelegung;
				}
			});

			// Find rooms or combinations of rooms
			for (int i = 0; i < possibleRooms.size(); i++) {
				List<Zimmer> roomCombo = new ArrayList<Zimmer>();
				int missingBelegungen = adults + children
						- possibleRooms.get(i).maxBelegung;
				roomCombo.add(possibleRooms.get(i));
				List<Zimmer> otherRooms = new ArrayList<Zimmer>(possibleRooms);

				otherRooms.remove(possibleRooms.get(i));
				// as long as we still have persons without a room and still
				// rooms available, //keep on adding rooms the room combination
				while (missingBelegungen > 0 && otherRooms.size() > 0) {
					List<Zimmer> bigEnough = findBigEnoughRooms(otherRooms,
							missingBelegungen);
					// If there is one or more rooms big enough, add the
					// smallest
					if (bigEnough.size() > 0) {
						roomCombo.add(bigEnough.get(bigEnough.size() - 1));
						missingBelegungen = missingBelegungen
								- bigEnough.get(bigEnough.size() - 1).maxBelegung;
						// Otherwise add the biggest room
					} else {
						roomCombo.add(otherRooms.get(0));
						missingBelegungen = missingBelegungen
								- otherRooms.get(0).maxBelegung;
						if (otherRooms.size() >= 1) {
							otherRooms = otherRooms.subList(1,
									otherRooms.size() - 1);
						} else {
							otherRooms = otherRooms.subList(0,
									otherRooms.size() - 1);
						}
					}
				}
				// if we have a proper room combination add it to the result
				if (missingBelegungen <= 0) {
					roomResults.add(roomCombo);
				}
			}
		}
		return roomResults;
	}

	private static List<Zimmer> findBigEnoughRooms(List<Zimmer> avalibleRooms,
			int persons) {
		List<Zimmer> bigEnoughRooms = new ArrayList<Zimmer>();
		for (Zimmer room : avalibleRooms) {
			if (room.getMaxBelegung() >= persons) {
				bigEnoughRooms.add(room);
			}
		}
		return bigEnoughRooms;
	}

	public static List<Zimmer> findAvailableRooms(Date from, Date to) {
		List<Zimmer> rooms = Zimmer.getAllRooms();
		List<Zimmer> availableRooms = new ArrayList<Zimmer>();
		for (Zimmer room : rooms) {
			if (room.isFreeInPeriod(from, to)) {
				availableRooms.add(room);
			}
		}
		return availableRooms;
	}

	// ##################################################################
	// ================== Setters and getters start =====================

	public List<Belegung> getBelegungen()
	{
		return belegungen;
	}

	public long getId()
	{
		return id;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public int getMaxBelegung()
	{
		return maxBelegung;
	}

	public void setMaxBelegung(int maxBelegung)
	{
		if (maxBelegung > 3) // this is because only prices up to 3 persons exist
			maxBelegung = 3;
		this.maxBelegung = maxBelegung;
	}

	public double getPreisEZ()
	{
		return preisEZ;
	}

	public void setPreisEZ(double preisEZ)
	{
		this.preisEZ = preisEZ;
	}

	public double getPreisDZ()
	{
		return preisDZ;
	}

	public void setPreisDZ(double preisDZ)
	{
		this.preisDZ = preisDZ;
	}

	public double getPreisDreifachbelegung()
	{
		return preisDreifachbelegung;
	}

	public void setPreisDreifachbelegung(double preisDreifachbelegung)
	{
		this.preisDreifachbelegung = preisDreifachbelegung;
	}

	public double getPreisEZmitKind()
	{
		return preisEZmitKind;
	}

	public void setPreisEZmitKind(double preisEZmitKind)
	{
		this.preisEZmitKind = preisEZmitKind;
	}

	public double getPreisEZmitZweiKindern()
	{
		return preisEZmitZweiKindern;
	}

	public void setPreisEZmitZweiKindern(double preisEZmitZweiKindern)
	{
		this.preisEZmitZweiKindern = preisEZmitZweiKindern;
	}

	public double getDZMitKind()
	{
		return DZMitKind;
	}

	public void setDZMitKind(double dZMitKind)
	{
		DZMitKind = dZMitKind;
	}

}
